package com.atguigu.gmall.index.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.annotation.GmallCache;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.util.DistributedLock;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class IndexService {
    @Resource
    private GmallPmsClient gmallPmsClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private DistributedLock distributedLock;

    @Resource
    private RedissonClient redissonClient;

    private static final String KEY_PREFIX = "index:cates:";
    private static final String LOCK_PREFIX = "index:cates:lock";
    public List<CategoryEntity> queryLevelCategories() {
        ResponseVo<List<CategoryEntity>> listResponseVo = gmallPmsClient.listResponseVoById(0l);
        List<CategoryEntity> categoryEntities = listResponseVo.getData();
        return categoryEntities;
    }

    @GmallCache(prefix = KEY_PREFIX,timeout = 129600,lock = LOCK_PREFIX,random = 14400)
    public List<CategoryEntity> querySubsCategoriesBuPid(Long pid) {
            ResponseVo<List<CategoryEntity>> listResponseVo = gmallPmsClient.queryLevelByPid(pid);
            List<CategoryEntity> categoryEntities = listResponseVo.getData();
            return categoryEntities;
    }
    public List<CategoryEntity> querySubsCategoriesBuPid2(Long pid) {
        //先查询缓存，如果缓存命中则返回
        String json = stringRedisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if (StringUtils.isNoneBlank(json)){
            return JSON.parseArray(json,CategoryEntity.class);
        }
        //添加分布式锁，防止缓存击穿
        RLock lock = redissonClient.getFairLock(LOCK_PREFIX + pid);
        lock.lock();

        try {
            //再次查询缓存，如果可以命中则直接返回（获取锁的过程中已经有其他请求把数据放入缓存）
            String json2 = stringRedisTemplate.opsForValue().get(KEY_PREFIX + pid);
            if (StringUtils.isNoneBlank(json2)){
                return JSON.parseArray(json2,CategoryEntity.class);
            }

            //缓存无法命中就查询数据库，并放入缓存
            ResponseVo<List<CategoryEntity>> listResponseVo = gmallPmsClient.queryLevelByPid(pid);
            List<CategoryEntity> categoryEntities = listResponseVo.getData();
            //放入缓存
            if (CollectionUtils.isEmpty(categoryEntities)){
                //如果未查到数据就将null存入redis防止穿透
                stringRedisTemplate.opsForValue().set(KEY_PREFIX+pid,JSON.toJSONString(categoryEntities),5, TimeUnit.MINUTES);
            }else {
                //添加随机值防止雪崩
                int tempTime = new Random().nextInt(10);
                stringRedisTemplate.opsForValue().set(KEY_PREFIX+pid,JSON.toJSONString(categoryEntities),90+tempTime, TimeUnit.MINUTES);
            }
            return categoryEntities;
        } finally {
        lock.unlock();
        }
    }

    public void test()  {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = distributedLock.tryLock("lock", uuid, 30);
        if (lock){
            String numTest = stringRedisTemplate.opsForValue().get("numTest");
            if (StringUtils.isEmpty(numTest)) {
                stringRedisTemplate.opsForValue().set("numTest", "1");
                return;
            }
            int num = Integer.parseInt(numTest);
            stringRedisTemplate.opsForValue().set("numTest", String.valueOf(++num));

            distributedLock.unLock("lock",uuid);
        }
    }



    public void test2()  {
        String uuid = UUID.randomUUID().toString();
        //加锁: setnx lock 111
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,5,TimeUnit.SECONDS);//设置过期时间防止死锁
        if (!lock){
            //重试
            try {
                Thread.sleep(40);//减少争抢  防止栈内存溢出
                test();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }else {
            String numTest = stringRedisTemplate.opsForValue().get("numTest");
            if (StringUtils.isEmpty(numTest)) {
                stringRedisTemplate.opsForValue().set("numTest", "1");
                return;
            }
            int num = Integer.parseInt(numTest);
            stringRedisTemplate.opsForValue().set("numTest", String.valueOf(++num));
            //解锁 del lock
//            if (StringUtils.equals(uuid,stringRedisTemplate.opsForValue().get("lock"))){
//                stringRedisTemplate.delete("lock");
//            }

            //添加redis的lua脚本，判断锁
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then " +
                    "   return redis.call('del',KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";
            stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"),uuid);
        }
    }

    public void test3()  {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        try {
            String numTest = stringRedisTemplate.opsForValue().get("numTest");
            if (StringUtils.isEmpty(numTest)) {
                stringRedisTemplate.opsForValue().set("numTest", "1");
                return;
            }
            int num = Integer.parseInt(numTest);
            stringRedisTemplate.opsForValue().set("numTest", String.valueOf(++num));
        } finally {
            lock.unlock();
        }


    }
}
